﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Infinity.Common;

namespace Infinity.Types
{
	public interface ILazySet<T, out TOut> : IEnumerable<TOut>
	{
		/// <summary>
		/// Set condition for filtering elements
		/// </summary>
		Func<T, bool> Condition { get; }

		/// <summary>
		/// Domain of set
		/// </summary>
		IEnumerable<T> Domain { get; }

		/// <summary>
		/// Expression of set
		/// </summary>
		Func<T, TOut> Expression { get; }
	}

	public class LazySet<T> : LazySet<T, T>
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		public LazySet(IEnumerable<T> domain, Func<T, bool> condition)
			: this(_ => _, domain, condition)
		{
		}

		/// <summary>
		/// Default constructor
		/// </summary>
		public LazySet(IEnumerable<T> domain)
			: this(domain, _ => true)
		{
		}

		/// <summary>
		/// Default constructor
		/// </summary>
		public LazySet(Func<T, T> expression, IEnumerable<T> domain, Func<T, bool> condition)
			: base(expression, domain, condition)
		{
		}
	}

	public class LazySet<T1, T2, TOut> : LazySet<Tuple<T1, T2>, TOut>
	{
		public LazySet(
			Func<Tuple<T1, T2>, TOut> expression, IEnumerable<Tuple<T1, T2>> domain, Func<Tuple<T1, T2>, bool> condition)
			: base(expression, domain, condition)
		{
		}

		public LazySet(Func<Tuple<T1, T2>, TOut> expression, IEnumerable<Tuple<T1, T2>> domain) : base(expression, domain)
		{
		}

		public virtual IEnumerable<T1> Domain1 { get; protected set; }
		public virtual IEnumerable<T2> Domain2 { get; protected set; }

		public override IEnumerable<Tuple<T1, T2>> Domain
		{
			get
			{
				return from a in Domain1
				       from b in Domain2
				       select new Tuple<T1, T2>(a, b);
			}
		}
	}

	public class LazySet<T, TOut> : ILazySet<T, TOut>
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		public LazySet(Func<T, TOut> expression, IEnumerable<T> domain, Func<T, bool> condition)
		{
			Domain = Ensure.NotNull(domain);
			Condition = Ensure.NotNull(condition);
			Expression = Ensure.NotNull(expression);
		}

		/// <summary>
		/// Default constructor
		/// </summary>
		public LazySet(Func<T, TOut> expression, IEnumerable<T> domain)
			: this(expression, domain, _ => true)
		{
		}

		public virtual IEnumerator<TOut> GetEnumerator()
		{
			Ensure.NotNull("Domain", Domain);
			Ensure.NotNull("Condition", Condition);
			Ensure.NotNull("Expression", Expression);

			foreach (var item in Domain)
				if (Condition(item))
					yield return Expression(item);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		public virtual Func<T, bool> Condition { get; private set; }
		public virtual IEnumerable<T> Domain { get; private set; }
		public Func<T, TOut> Expression { get; private set; }
	}
}